{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 问题：装饰器的用法是什么？"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true
   },
   "source": [
    "面试官想从这道题中考察面试人什么？\n",
    "很多新手在看到装饰器的时候都会一头雾水，因为装饰器大多涉及函数嵌套，以及变量作用域，这也是我们为什么要先了解闭包的原因。\n",
    "\n",
    "关于这道题，面试官可能会从最基本的问题开始，如何阅读一个装饰器的代码段以及知道它具体在干什么，又或者 Python 如何执行装饰器。问题再深入点，如何写一个最简单的装饰器应用（无参数版），比如计时器，注册机等。带有参数的装饰器又是如何实现呢多个装饰器呢？等等。但无论题目怎么问，几个基本的东西必须知道：\n",
    "\n",
    "装饰器句法\n",
    "装饰器的本质\n",
    "Python 何时载入装饰器，何时运行\n",
    "答案\n",
    "一个装饰器（decorator）就是一个函数，它接受一个函数作为参数并返回一个新的函数。\n",
    "\n",
    "基本用法：\n",
    "\n",
    "@decorator\n",
    "def func(*args, **kwargs):\n",
    "    ...\n",
    "它等价于：\n",
    "\n",
    "def func(*args, **kwargs):\n",
    "    ...\n",
    "func = decorator(func) # func是一个函数对象，只有加了（）才是真正调用这个方法\n",
    "拓展\n",
    "使用装饰器是元编程技术的一种。首先，我们先要知道元编程是什么呢？简单点说，它是关于创建操作源代码的函数和类，在运行时去改变程序的行为。这也是 clean code 的守则之一：不要做重复的事情（Don’t repeat yourself）。而装饰器就是给一个函数增加额外的操作，并且可以被复用。\n",
    "\n",
    "我们先从装饰器的句法开始。Python 中，@符号就是表示装饰器的语法糖。\n",
    "\n",
    "装饰器的本质就是，它是一个可调用的对象（通常都是指函数），它的参数是另外一个函数。\n",
    "\n",
    "Tips: 具有__call__()方法的对象都是可调用对象。\n",
    "\n",
    "下面我们还是先看一段代码片段。把下面这段代码保存为test.py并且在命令窗口直接运行python test.py。\n",
    "\n",
    "registry = []\n",
    "\n",
    "def register(func):\n",
    "    print('register func: ', func.__name__)\n",
    "    resgistry.append(func)\n",
    "\n",
    "    return func # 必须返回原函数对象func\n",
    "\n",
    "# 这里的@register就相当于执行了 func1 = register(func1)\n",
    "# 如果上面没有返回`func`，那么func1就是一个None，无法去调用它\n",
    "@register\n",
    "def func1():\n",
    "    print('running func1')\n",
    "\n",
    "@register\n",
    "def func2():\n",
    "    print('running func2')\n",
    "\n",
    "def main():\n",
    "    print('registered functions: ', registry)\n",
    "    func1()\n",
    "    func2()\n",
    "\n",
    "if __name__ == \"__main__\":\n",
    "    main()\n",
    "我们来看看运行这个模块的输出结果是什么？\n",
    "\n",
    "> python test.py\n",
    "line1: register func:  func1\n",
    "line2: register func:  func2\n",
    "line3: registered functions:  [<function func1 at 0x10de2fe60>, <function func2 at 0x10de2fdd0>]\n",
    "line4: running func1\n",
    "line5: running func2\n",
    "请忽略line1之类的打印，为了方便解释，那是我后期加上去的。\n",
    "\n",
    "下面我来看看这些打印说明了什么？\n",
    "\n",
    "从 line1，line2 的打印，装饰器在加载模块时是立即执行的。\n",
    "line3 这里真正开始运行main()\n",
    "line4 和 line5 说明被装饰的函数在明确调用是运行。\n",
    "以上这些都是装饰器的基础知识，那么下面讲几个高级点的用法。上面那个装饰器的例子中，我们没有对传入的func做任何事情，只是把它注册到了一个全局变量中。\n",
    "\n",
    "我们先从实现一个最简单的计时器的装饰器开始。\n",
    "\n",
    "import time\n",
    "\n",
    "def timeit(func):\n",
    "    def wrapper(*args, **kwargs): # 闭包，func是一个自由变量\n",
    "        \"\"\"\n",
    "        This is a wrapper.\n",
    "        \"\"\"\n",
    "        start = time.time()\n",
    "\n",
    "        # 调用原始函数，并使用*args, **kwargs来接受任意参数，这样就确保了这个装饰器可以作用于任何函数\n",
    "        # 因为需要在装饰器内部去执行传入的函数，所以要加一层函数的包装器wrapper。\n",
    "        result = func(*args, **kwargs)\n",
    "        end = time.time()\n",
    "\n",
    "        print(func.__name__, end - start) # 该装饰器真正的任务，即对函数实行额外计时的操作\n",
    "        return result # 返回原始函数调用后的结果\n",
    "    return wrapper\n",
    "\n",
    "@timeit\n",
    "def sleep_time(seconds):\n",
    "    \"\"\"\n",
    "    sleep timer\n",
    "    \"\"\"\n",
    "    time.sleep(seconds)\n",
    "\n",
    ">>> sleep_time(10)\n",
    ">>> sleep_time.__name__ # 输出为包装器的函数名\n",
    ">>> sleep_time.__doc__ # 输出为包装器的文档帮助\n",
    "你会发现这个装饰器有个缺点，原函数的元信息全部被覆盖了。很多时候，函数的元信息在调试，内省的时候都很重要，怎样才可以保留原函数的元信息呢？\n",
    "\n",
    "from functools import wraps\n",
    "\n",
    "def timeit(func):\n",
    "    @wraps(func) # 加这么一句就可以保留元信息\n",
    "    def wrapper(*args, **kwargs):\n",
    "        ...\n",
    "        # 这里和上面的代码一样\n",
    "        return result # 返回原始函数调用后的结果\n",
    "    return wrapper\n",
    "多个装饰器怎么理解呢？像这样：\n",
    "\n",
    "@a\n",
    "@b\n",
    "def func():\n",
    "    ...\n",
    "它等价于func=a(b(func))。\n",
    "\n",
    "最后，再讲一个可以传参数的装饰器。\n",
    "\n",
    "@decorator(a,b,c)\n",
    "def func(*args, **kwargs):\n",
    "    ...\n",
    "它等价于：\n",
    "\n",
    "def func(*args, **kwargs):\n",
    "    ...\n",
    "func = decorator(a, b, c)(func)\n",
    "Tips: decorator(a, b, c)返回的是一个可调用对象，而且它接受函数作为参数并包装它，即一个新的装饰器，比如我们可以把decorator(a, b, c)用decorator1去表示，那么func=decorator1(func)。换言之，最外层我们可以把它看成一个装饰器工厂，把参数传给它，返回一个装饰器，然后再把它应用到被装饰的函数上。\n",
    "\n",
    "import time\n",
    "from functools import wraps\n",
    "\n",
    "def timeit(token): # 可以接受参数token\n",
    "    def decorator(func): # 这个内部函数是真正的装饰器\n",
    "        @wraps(func)\n",
    "        def wrapper(*args, **kwargs):\n",
    "            print('{0} {1} {0:>10s}'.format(token * 10, func.__name__)) # 根据不同的token参数去打印\n",
    "            start = time.time()\n",
    "            result = func(*args, **kwargs)\n",
    "            end = time.time()\n",
    "            print('{0} Total time: {1:.2f} {0:>10s}'.format(token * 10, end - start))\n",
    "            return result\n",
    "        return wrapper # decorator装饰器必须返回以后函数\n",
    "    return decorator # 装饰器工厂函数返回一个装饰器\n",
    "\n",
    "@timeit('#') # 传参数给装饰器\n",
    "def sleep_time(seconds):\n",
    "    time.sleep(seconds)\n",
    "\n",
    ">>> sleep_time(5)\n",
    "小结\n",
    "这三个小节主要讲了函数相关的面试题，在 Python 里，“函数是一等对象”，这让它拥有了很多特性：\n",
    "\n",
    "匿名函数，可以将它赋值给变量或传递给另外一个函数\n",
    "函数式编程思维，尽量避免可变状态，用数学函数的思维去编程，易于理解\n",
    "闭包，嵌套在一个函数中，并延伸外部作用域的变量\n",
    "装饰器，附加在另一个函数上，增强函数的行为"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}